package com.github.pagehelper;

import com.github.pagehelper.aop.DecryptField;
import com.github.pagehelper.util.CryptPojoUtils;
import com.github.pagehelper.util.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @author fangzs
 * @date 2018/10/9 10:03
 * @description
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
        })
public class DBInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
        String methodName = invocation.getMethod().getName();
        Object parameter = invocation.getArgs()[1];
        BoundSql sql = statement.getBoundSql(parameter);

        /**
         * 拦截批量插入操作不仅繁琐，而且为了通用逐一通过反射加密不妥
         * 如果有批量操作，最好在传递参数之前，向list中添加之前就加密
         */
        if (StringUtils.equalsIgnoreCase("update", methodName)
                || StringUtils.equalsIgnoreCase("insert", methodName)) {
            CryptPojoUtils.encryptField(parameter);
        }

        Object returnValue = invocation.proceed();

        try {
            if (returnValue instanceof ArrayList<?>) {
                List<?> list = (ArrayList<?>) returnValue;
                if (null == list || 1 > list.size()) {
                    return returnValue;
                }
                Object obj = list.get(0);
                // 这里虽然list不是空，但是返回字符串等有可能为空
                if (null == obj) {
                    return returnValue;
                }
                // 判断第一个对象是否有DecryptField注解
                Field[] fields = obj.getClass().getDeclaredFields();
                int len;
                if (null != fields && 0 < (len = fields.length)) {
                    // 标记是否有解密注解
                    boolean isD = false;
                    for (int i = 0; i < len; i++) {
                        /**
                         * 由于返回的是同一种类型列表，因此这里判断出来之后可以保存field的名称
                         * 之后处理所有对象直接按照field名称查找Field从而改之即可
                         * 有可能该类存在多个注解字段，所以需要保存到数组（项目中目前最多是2个）
                         * @TODO 保存带DecryptField注解的字段名称到数组，按照名称获取字段并解密
                         * */
                        if (fields[i].isAnnotationPresent(DecryptField.class)) {
                            isD = true;
                            break;
                        }
                    }
                    // 将含有DecryptField注解的字段解密
                    if (isD) {
                        list.forEach(l -> CryptPojoUtils.decryptField(l));
                    }
                }
            }

        } catch (Exception e) {
            // 打印异常，由于拦截器本身抛出异常，比如拦截到很奇葩的返回，应算正常
            // 直接返回原结果即可
            e.printStackTrace();
            return returnValue;
        }
        return returnValue;
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
